﻿using System.Diagnostics;
using System.IO;
using System.Security.Cryptography;

namespace EncryptionLib.Util
{
    public class SymmetricAlgo
    {

        public static SymmetricAlgorithm CreateSymmetricAlgorithm(SymmetricAlgorithms algo)
        {
            switch (algo)
            {
                case SymmetricAlgorithms.RC2:
                    return RC2.Create();
                case SymmetricAlgorithms.Rijndael:
                    return Rijndael.Create();
                case SymmetricAlgorithms.DES:
                    return DES.Create();
                default:
                    return TripleDES.Create();
            }
        }

        public static int GetDESKeyLen()
        {
            SymmetricAlgorithm algo = Rijndael.Create();
            //if (algo.KeySize % 8 == 0)
            //    return algo.KeySize;

            foreach(KeySizes k in algo.LegalKeySizes)
            {
                for(int i = k.MinSize; i <= k.MaxSize; ++i)
                {
                    if (i % 8 == 0)
                        return i;
                }
            }
            return -1;
        }

        public static byte[] DESEncrypt(byte[] arrPlainData, byte[] arrPassword, byte[] arrIV,
            CipherMode cipherMode, PaddingMode paddingMode)
        {
            MemoryStream ms;
            CryptoStream cs;

            SymmetricAlgorithm algo = Rijndael.Create();
            algo.Key = arrPassword;
            algo.IV = arrIV;
            algo.Mode = cipherMode;
            algo.Padding = paddingMode;

            ms = new MemoryStream();
            cs = new CryptoStream(ms,
               algo.CreateEncryptor(),
               CryptoStreamMode.Write);

            // bullsh*t. We HAVE to FlushFinalBlock(), or we'll rut at a "Bad Error" exception.
            // f*ck MS! x-(
            cs.Write(arrPlainData, 0, arrPlainData.Length);
            cs.FlushFinalBlock();
            byte[] arrEncryptedData = ms.ToArray();

            if (cs != null)
                cs.Close();
            if (ms != null)
                ms.Close();

            return arrEncryptedData;
        }

        public static byte[] DESDecrypt(byte[] arrEncryptedData, byte[] arrPassword, byte[] arrIV,
            CipherMode cipherMode, PaddingMode paddingMode)
        {
            MemoryStream ms = null;
            CryptoStream cs = null;

            SymmetricAlgorithm algo = Rijndael.Create();
            algo.Key = arrPassword;
            algo.IV = arrIV;
            algo.Mode = cipherMode;
            algo.Padding = paddingMode;

            ms = new MemoryStream(arrEncryptedData);
            cs = new CryptoStream(
               ms,
               algo.CreateDecryptor(),
               CryptoStreamMode.Read);

            //read plaintext bytes from crypto stream
            byte[] plainbytes = new byte[arrEncryptedData.Length];
            cs.Read(plainbytes, 0, arrEncryptedData.Length);
            //Debug.Assert(ii == arrEncryptedData.Length);
            

            if (cs != null)
                cs.Close();
            if (ms != null)
                ms.Close();

            return plainbytes;
        }

        public static void DESEncrypt(Stream streamInput, Stream streamOutput,
            byte[] arrPassword, byte[] arrIV, CipherMode cipherMode, PaddingMode paddingMode)
        {
            streamInput.Seek(0, SeekOrigin.Begin);
            streamOutput.Seek(0, SeekOrigin.Begin);

            SymmetricAlgorithm algo = Rijndael.Create();
            algo.Key = arrPassword;
            algo.IV = arrIV;
            algo.Mode = cipherMode;
            algo.Padding = paddingMode;

            using(CryptoStream cs = new CryptoStream(streamOutput,
               algo.CreateEncryptor(), CryptoStreamMode.Write))
            {
                // bullsh*t. We HAVE to FlushFinalBlock(), or we'll rut at a "Bad Error" exception.
                // f*ck MS! x-(
                const int BUFFERLEN = 1024;
                while (streamInput.Position < streamInput.Length)
                {
                    byte[] arrInData = new byte[BUFFERLEN];
                    int iRead = streamInput.Read(arrInData, 0, BUFFERLEN);

                    if (iRead > 0 && iRead != BUFFERLEN)
                    {
                        arrInData = (byte[])SerializerHelper.ResizeArray(arrInData, iRead);
                    }

                    if (iRead > 0)
                    {
                        cs.Write(arrInData, 0, arrInData.Length);
                    }
                }
                cs.FlushFinalBlock();
                if (cs != null)
                    cs.Close();
            }
        }

        public static void DESDecrypt(Stream streamInput, Stream streamOutput,
            byte[] arrPassword, byte[] arrIV, CipherMode cipherMode, PaddingMode paddingMode)
        {
            streamInput.Seek(0, SeekOrigin.Begin);
            streamOutput.Seek(0, SeekOrigin.Begin);

            SymmetricAlgorithm algo = Rijndael.Create();
            algo.Key = arrPassword;
            algo.IV = arrIV;
            algo.Mode = cipherMode;
            algo.Padding = paddingMode;

            using (CryptoStream cs = new CryptoStream(streamInput,
               algo.CreateDecryptor(), CryptoStreamMode.Read))
            {
                const int BUFFERLEN = 1024;
                while (streamInput.Position < streamInput.Length)
                {
                    byte[] arrPlainData = new byte[BUFFERLEN];
                    int iRead = cs.Read(arrPlainData, 0, BUFFERLEN);
                    streamOutput.Write(arrPlainData, 0, iRead);
                }
                if (cs != null)
                    cs.Close();
            }
        }
    }
}
